/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2003 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "types.h"
#include "x6502.h"
#include "fceu.h"
#include "debug.h"
#include "cart.h"


void FCEUI_DumpVid(const char *fname, uint32 start, uint32 end)
{
 FILE *fp=FCEUD_UTF8fopen(fname,"wb");
 fceuindbg=1;
 start&=0x1FFF;
 end&=0x1FFF;
 for(;start<=end;start++)
  fputc(VPage[start>>10][start],fp);
 fclose(fp);
 fceuindbg=0;
}

void FCEUI_DumpMem(const char *fname, uint32 start, uint32 end)
{
 FILE *fp=FCEUD_UTF8fopen(fname,"wb");
 fceuindbg=1;
 for(;start<=end;start++)
  fputc(ARead[start](start),fp);
 fclose(fp);
 fceuindbg=0;
}

void FCEUI_LoadMem(const char *fname, uint32 start, int hl)
{
 int t;

 FILE *fp=FCEUD_UTF8fopen(fname,"rb");
 while((t=fgetc(fp))>=0)
 {
  if(start>0xFFFF) break;
  if(hl)
  {
   extern uint8 *Page[32];
   if(Page[start/2048])
    Page[start/2048][start]=t;
  }
  else
   BWrite[start](start,t);
  start++;
 }
 fclose(fp);
}

#ifdef FCEUDEF_DEBUGGER

static char *fstrings[12]=
{
  "#$%02X",       // immediate
  "$%04X",  // RELATIVE(jump)
  "$%02X",  // Z
  "$%02X,X",      // Z,x
  "$%02X,Y",      // Z,y
  "$%04X",  //ABS
  "$%04X,X",      // ABS,x
  "$%04X,Y",      // ABS,y
  "($%04X)",      // IND
  "($%02X,X)",    // INX
  "($%02X),Y",    // INY
  ""
};
static int flengths[12]={1,1,1,1,1,2,2,2,2,1,1,0};

#define IMD(x)  ((0<<16)|x)
#define  REL(x)  ((1<<16)|x)
#define  ZP(x)  ((2<<16)|x)
#define  ZPX(x)  ((3<<16)|x)
#define  ZPY(x)  ((4<<16)|x)
#define  ABS(x)  ((5<<16)|x)
#define  ABX(x)  ((6<<16)|x)
#define ABY(x)  ((7<<16)|x)
#define  IND(x)  ((8<<16)|x)
#define  INX(x)  ((9<<16)|x)
#define  INY(x)  ((10<<16)|x)
#define IMP(x)  ((11<<16)|x)

typedef struct {
  char *name;
  int type;       /* 1 for read, 2 for write, 3 for r then write. */
  int32 modes[10];
} OPS;
#define NUMOPS 56
static OPS optable[NUMOPS]=
{
 {"BRK",0,{IMP(0x00),-1}},
 {"RTI",0,{IMP(0x40),-1}},
 {"RTS",0,{IMP(0x60),-1}},
 {"PHA",2,{IMP(0x48),-1}},
 {"PHP",2,{IMP(0x08),-1}},
 {"PLA",1,{IMP(0x68),-1}},
 {"PLP",1,{IMP(0x28),-1}},
 {"JMP",0,{ABS(0x4C),IND(0x6C),-1}},
 {"JSR",0,{ABS(0x20),-1}},
 {"TAX",0,{IMP(0xAA),-1}},
 {"TXA",0,{IMP(0x8A),-1}},
 {"TAY",0,{IMP(0xA8),-1}},
 {"TYA",0,{IMP(0x98),-1}},
 {"TSX",0,{IMP(0xBA),-1}},
 {"TXS",0,{IMP(0x9A),-1}},
 {"DEX",0,{IMP(0xCA),-1}},
 {"DEY",0,{IMP(0x88),-1}},
 {"INX",0,{IMP(0xE8),-1}},
 {"INY",0,{IMP(0xC8),-1}},
 {"CLC",0,{IMP(0x18),-1}},
 {"CLD",0,{IMP(0xD8),-1}},
 {"CLI",0,{IMP(0x58),-1}},
 {"CLV",0,{IMP(0xB8),-1}},
 {"SEC",0,{IMP(0x38),-1}},
 {"SED",0,{IMP(0xF8),-1}},
 {"SEI",0,{IMP(0x78),-1}},
 {"NOP",0,{IMP(0xEA),-1}},
 {"ASL",1,{IMP(0x0a),ZP(0x06),ZPX(0x16),ABS(0x0E),ABX(0x1E),-1}},
 {"DEC",3,{ZP(0xc6),ZPX(0xd6),ABS(0xcE),ABX(0xdE),-1}},
 {"INC",3,{ZP(0xe6),ZPX(0xf6),ABS(0xeE),ABX(0xfE),-1}},
 {"LSR",3,{IMP(0x4a),ZP(0x46),ZPX(0x56),ABS(0x4E),ABX(0x5E),-1}},
 {"ROL",3,{IMP(0x2a),ZP(0x26),ZPX(0x36),ABS(0x2E),ABX(0x3E),-1}},
 {"ROR",3,{IMP(0x6a),ZP(0x66),ZPX(0x76),ABS(0x6E),ABX(0x7E),-1}},
 {"ADC",1,{IMD(0x69),ZP(0x65),ZPX(0x75),ABS(0x6D),ABX(0x7d),ABY(0x79),
   INX(0x61),INY(0x71),-1}},
 {"AND",1,{IMD(0x29),ZP(0x25),ZPX(0x35),ABS(0x2D),ABX(0x3d),ABY(0x39),
   INX(0x21),INY(0x31),-1}},
 {"BIT",1,{ZP(0x24),ABS(0x2c),-1}},
 {"CMP",1,{IMD(0xc9),ZP(0xc5),ZPX(0xd5),ABS(0xcD),ABX(0xdd),ABY(0xd9),
   INX(0xc1),INY(0xd1),-1}},
 {"CPX",1,{IMD(0xe0),ZP(0xe4),ABS(0xec),-1}},
 {"CPY",1,{IMD(0xc0),ZP(0xc4),ABS(0xcc),-1}},
 {"EOR",1,{IMD(0x49),ZP(0x45),ZPX(0x55),ABS(0x4D),ABX(0x5d),ABY(0x59),
   INX(0x41),INY(0x51),-1}},
 {"LDA",1,{IMD(0xa9),ZP(0xa5),ZPX(0xb5),ABS(0xaD),ABX(0xbd),ABY(0xb9),
   INX(0xa1),INY(0xb1),-1}},
 {"LDX",1,{IMD(0xa2),ZP(0xa6),ZPY(0xB6),ABS(0xae),ABY(0xbe),-1}},
 {"LDY",1,{IMD(0xa0),ZP(0xa4),ZPX(0xB4),ABS(0xac),ABX(0xbc),-1}},
 {"ORA",1,{IMD(0x09),ZP(0x05),ZPX(0x15),ABS(0x0D),ABX(0x1d),ABY(0x19),
   INX(0x01),INY(0x11),-1}},
 {"SBC",1,{IMD(0xEB),IMD(0xe9),ZP(0xe5),ZPX(0xf5),ABS(0xeD),ABX(0xfd),ABY(0xf9),
   INX(0xe1),INY(0xf1),-1}},
 {"STA",2,{ZP(0x85),ZPX(0x95),ABS(0x8D),ABX(0x9d),ABY(0x99),
   INX(0x81),INY(0x91),-1}},
 {"STX",2,{ZP(0x86),ZPY(0x96),ABS(0x8E),-1}},
 {"STY",2,{ZP(0x84),ZPX(0x94),ABS(0x8C),-1}},
 {"BCC",1,{REL(0x90),-1}},
 {"BCS",1,{REL(0xb0),-1}},
 {"BEQ",1,{REL(0xf0),-1}},
 {"BNE",1,{REL(0xd0),-1}},
 {"BMI",1,{REL(0x30),-1}},
 {"BPL",1,{REL(0x10),-1}},
 {"BVC",1,{REL(0x50),-1}},
 {"BVS",1,{REL(0x70),-1}},
};

uint16 FCEUI_Disassemble(void *XA, uint16 a, char *stringo)
{
 X6502 *X=XA;
 uint8 buf;
 unsigned int arg;
 int32 info;
 int x;
 int y;

 info=-1;
 fceuindbg=1;

  buf=ARead[a](a);
  a++;

  for(x=0;x<NUMOPS;x++)
  {
   y=0;
   while(optable[x].modes[y]>=0)
   {
    if((optable[x].modes[y]&0xFF)==buf)
    {
     info=optable[x].modes[y];
     goto endy;
    }
    y++;
   }
  }

  endy:
  sprintf(stringo,"%02X ",buf);
  if(info>=0)
  {
   int z=flengths[(info>>16)];

   if(z)
   {
    arg=ARead[a](a);
    sprintf(stringo+strlen(stringo),"%02X ",arg);
    a++;
    if(z==2) {arg|=ARead[a](a)<<8;sprintf(stringo+strlen(stringo),"%02X ",arg>>8);a++;}
    else
     strcat(stringo,"   ");

    if((info>>16)==1)   /* Relative branch */
     arg=a+(char)arg;
    sprintf(stringo+strlen(stringo),"%s ",optable[x].name);
    sprintf(stringo+strlen(stringo),fstrings[info>>16],arg);
/*
  0  "#$%02X",       // immediate
  1  "$%04X",  // RELATIVE(jump)
  2  "$%02X",  // Z
  3  "$%02X,X",      // Z,x
  4  "$%02X,Y",      // Z,y
  5  "$%04X",  //ABS
  6 "$%04X,X",      // ABS,x
  7  "$%04X,Y",      // ABS,y
  8  "($%04X)",      // IND
  9  "($%02X,X)",    // INX
  10 "($%02X),Y",    // INY
  11 #define IMP(x)  ((11<<16)|x)
*/
    {
     unsigned int tmp;
     switch(info>>16)
     {
      case 2:tmp=arg;
       if(optable[x].type&1)
       {
        sprintf(stringo+strlen(stringo),"    @ $%04X",tmp);
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       }
       break;
      case 3:tmp=(arg+X->X)&0xff;
       sprintf(stringo+strlen(stringo),"    @ $%04X",tmp);
       if(optable[x].type&1)
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       break;
      case 4:tmp=(arg+X->Y)&0xff;
       sprintf(stringo+strlen(stringo),"    @ $%04X",tmp);
       if(optable[x].type&1)
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       break;
      case 5:tmp=arg;
       if(optable[x].type&1)
       {
        sprintf(stringo+strlen(stringo),"  @ $%04X",tmp);
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       }
       break;
      case 6:tmp=(arg+X->X)&0xffff;
       sprintf(stringo+strlen(stringo),"  @ $%04X",tmp);
       if(optable[x].type&1)
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       break;
      case 7:tmp=(arg+X->Y)&0xffff;
       sprintf(stringo+strlen(stringo),"  @ $%04X",tmp);
       if(optable[x].type&1)
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       break;
      case 8:tmp=ARead[arg](arg)|(ARead[(arg+1)&0xffff]((arg+1)&0xffff)<<8);
       sprintf(stringo+strlen(stringo)," $%04X",tmp);
       break;
      case 9:tmp=(arg+X->X)&0xFF;
       tmp=ARead[tmp](tmp) | (ARead[(tmp+1)&0xFF]((tmp+1)&0xFF)<<8);
       sprintf(stringo+strlen(stringo),"  @ $%04X",tmp);
       if(optable[x].type&1)
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       break;
      case 10:tmp=ARead[arg](arg) | (ARead[(arg+1)&0xFF]((arg+1)&0xFF)<<8);
       tmp=(tmp+X->Y)&0xFFFF;
       sprintf(stringo+strlen(stringo),"  @ $%04X",tmp);
       if(optable[x].type&1)
        sprintf(stringo+strlen(stringo)," = $%02X",ARead[tmp](tmp));
       break;

     }
    }


   }
   else
   {
    strcat(stringo,"      ");
    strcat(stringo,optable[x].name);
   }
  }
  else
   sprintf(stringo+strlen(stringo),"      .db $%02X",buf);
 fceuindbg=0;
 return(a);
}

void FCEUI_MemDump(uint16 a, int32 len, void (*callb)(uint16 a, uint8 v))
{
 fceuindbg=1;
 while(len)
 {
  callb(a,ARead[a](a));
  a++;
  len--;
 }
 fceuindbg=0;
}

uint8 FCEUI_MemSafePeek(uint16 A)
{
 uint8 ret;

 fceuindbg=1;
 ret=ARead[A](A);
 fceuindbg=0;
 return(ret);
}

void FCEUI_MemPoke(uint16 a, uint8 v, int hl)
{
 extern uint8 *Page[32];
 if(hl)
 {
  if(Page[a/2048])
   Page[a/2048][a]=v;
 }
 else
  BWrite[a](a,v);
}

typedef struct __BPOINT {
  struct __BPOINT *next;
  void (*Handler)(X6502 *X, int type, unsigned int A);
  unsigned int A[2];
  int type;
} BPOINT;

static BPOINT *BreakPoints=NULL;
static BPOINT *LastBP=NULL;

static void (*CPUHook)(X6502 *)=NULL;

static int FindBPoint(X6502 *X, int who, unsigned int A)
{
 BPOINT *tmp;

 tmp=BreakPoints;
 while(tmp)
 {
  if(tmp->type&who)
  {
   if(tmp->type&BPOINT_PC)
    if(X->PC!=A) goto don;  /* Doesn't match, so go on. */

   if((A>=tmp->A[0]) && (A<=tmp->A[1]))  /* Whee, match. */
   {
    tmp->Handler(X,tmp->type,A);
    return(1);
   }
  }
  don:
  tmp=tmp->next;
 }
 return(0);
}

static uint8 ReadHandler(X6502 *X, unsigned int A)
{
 extern X6502 XSave;

 if(X->preexec)
  FindBPoint(&XSave,BPOINT_READ,A);
 return(ARead[A](A));
}

static void WriteHandler(X6502 *X, unsigned int A, uint8 V)
{
 extern X6502 XSave;

 if(X->preexec)
  FindBPoint(&XSave,BPOINT_WRITE,A);
 else
  BWrite[A](A,V);
}

int FCEUI_AddBreakPoint(int type, unsigned int A1, unsigned int A2,
    void (*Handler)(X6502 *, int type, unsigned int A))
{
 BPOINT *tmp;

 tmp=(BPOINT *)malloc(sizeof(BPOINT));

 tmp->A[0]=A1;
 tmp->A[1]=A2;
 tmp->Handler=Handler;
 tmp->type=type;
 tmp->next=0;

 if(BreakPoints==NULL)
  BreakPoints=tmp;
 else
  LastBP->next=tmp;

 LastBP=tmp;

 X6502_Debug(CPUHook,ReadHandler,WriteHandler);
 return(1);
}

int FCEUI_SetBreakPoint(uint32 w, int type, unsigned int A1, unsigned int A2,
    void (*Handler)(X6502 *, int type, unsigned int A))
{
 uint32 x=0;
 BPOINT *tmp;

 tmp=BreakPoints;

 while(tmp)
 {
  if(w==x)
  {
   tmp->type=type;
   tmp->A[0]=A1;
   tmp->A[1]=A2;
   tmp->Handler=Handler;
   return(1);
  }
  x++;
  tmp=tmp->next;
 }
 return(0);
}

int FCEUI_GetBreakPoint(uint32 w, int *type, unsigned int *A1, unsigned int *A2,
    void (**Handler)(X6502 *, int type, unsigned int A))
{
 uint32 x=0;
 BPOINT *tmp;

 tmp=BreakPoints;

 while(tmp)
 {
  if(w==x)
  {
   *type=tmp->type;
   *A1=tmp->A[0];
   *A2=tmp->A[1];
   *Handler=tmp->Handler;
   return(1);
  }
  x++;
  tmp=tmp->next;
 }
 return(0);
}

int FCEUI_ListBreakPoints(int (*callb)(int type, unsigned int A1, unsigned int A2,
    void (*Handler)(X6502 *, int type, unsigned int A) ))
{
 BPOINT *tmp;
 tmp=BreakPoints;
 while(tmp)
 {
  callb(tmp->type,tmp->A[0],tmp->A[1],tmp->Handler);
  tmp=tmp->next;
 }
 return(1);
}

int FCEUI_DeleteBreakPoint(uint32 w)
{
 BPOINT *tmp,*prev=NULL;
 uint32 x=0;

 tmp=BreakPoints;

 while(tmp)
 {
  if(w==x)
  {
   if(prev)      /* Not the first breakpoint. */
   {
    if(tmp->next)    /* More breakpoints. */
     prev->next=tmp->next;
    else      /* This is the last breakpoint. */
    {
     prev->next=0;
     LastBP=prev;
    }
   }
   else        /* The first breakpoint. */
   {
    if(tmp->next)    /* More breakpoints. */
     BreakPoints=tmp->next;
    else
    {
     BreakPoints=LastBP=0;  /* No more breakpoints. */
     /* Update the CPU hooks. */
     X6502_Debug(CPUHook,BreakPoints?ReadHandler:0,BreakPoints?WriteHandler:0);
    }
   }
   free(tmp);
   return(1);
  }
  prev=tmp;
  tmp=tmp->next;
  x++;
 }
 return(0);
}

void FCEUI_SetCPUCallback(void (*callb)(X6502 *X))
{
 CPUHook=callb;
 X6502_Debug(CPUHook,BreakPoints?ReadHandler:0,BreakPoints?WriteHandler:0);
}
#endif
